using Digitalmes.Application.Scada.Models;
using Digitalmes.Application.Services;
using Digitalmes.Domain.Entities.Mes.Processes;
using Digitalmes.Domain.Entities.Mes.Productions;
using Digitalmes.Domain.Entities.Mes.Reworks;
using ThingsEdge.Exchange.Contracts;

namespace Digitalmes.Application.Scada.Services.Impl;

internal sealed class OutboundService(
    IRepository<ProdCurrentState> currentStateRepo,
    IRepository<ProdInbound> inboundRepo,
    IRepository<ProdOutbound> outboundRepo,
    IRepository<ProdStepExecution> stepExecutionRepo,
    IRepository<ProdStepData> stepDataRepo,
    IRepository<ReworkDoc> reworkDocRepo, 
    IRepository<ProcStepStandard> stepStandardRepo,
    IRoutingQueryService routingQueryService,
    IWorkstationQueryService workstationQueryService,
    IFeedingMaterialService feedingMaterialService,
    IWorkOrderService workOrderService) : IOutboundService
{
    public async Task<ScadaResult> ArchiveAsync(OutboundInput input, CancellationToken cancellationToken = default)
    {
        // 若要数据异常时回滚，可以考虑使用工作单元
        // using var uow = uowManage.CreateUow(); // 注入 IUnitOfWorkManage
        // var inboundRepo = uow.GetRepository<ProdInbound>;
        // doSomething ...
        // uow.Commit();

        // 检索工位信息
        var workstation = await workstationQueryService.GetWorkstationAsync(input.LineCode, input.WsCode);
        if (workstation == null)
        {
            return ScadaResult.Error(ErrorCode.E1203);
        }

        // 检测产品是否处于此工位
        var currentState = await currentStateRepo.FirstOrDefaultAsync(m => m.Sn == input.SN && m.WorkstationId == workstation.Id, cancellationToken);
        if (currentState == null)
        {
            return ScadaResult.Error(ErrorCode.E1204);
        }

        // 在产品在当前工位的进/出站状态
        if (currentState.Stage is not WorkStageEnum.In)
        {
            return ScadaResult.Error(ErrorCode.E1205);
        }

        // 获取产品在该工序的最近一次的进站信息
        var inbound = await inboundRepo.LastOrDefaultAsync(m => m.Sn == currentState.Sn && m.OperationId == currentState.OperationId, cancellationToken);
        if (inbound == null)
        {
            return ScadaResult.Error(ErrorCode.E1206);
        }

        // 校验关键物料是否有遗漏
        var routingStep = await routingQueryService.GetRoutingStepAsync(inbound.RoutingId, inbound.OperationId);
        if (routingStep.IsCheckMaterial)
        {
            var hasDone = await feedingMaterialService.CheckKeyMaterialDoneAsync(routingStep.Id,
                currentState.Sn,
                currentState.LineId,
                currentState.OperationId);
            if (!hasDone)
            {
                return ScadaResult.Error(ErrorCode.E1207);
            }
        }

        // 检查是否存在批次料，存在则进行绑定
        await feedingMaterialService.CheckAndLoadBatchMaterialAsync(routingStep.Id,
            currentState.Sn,
            currentState.LineId,
            currentState.WorkstationId,
            currentState.OperationId,
            currentState.OperationCode);

        // 新增产品出站记录
        ProdOutbound outbound = new()
        {
            InboundId = inbound.Id,
            PassResult = input.PassResult,
            OutboundTime = DateTime.Now,
            CycleTime = (input.CycleTime is null or < 0.1) ? (DateTime.Now - inbound.InboundTime).TotalSeconds : input.CycleTime.Value,
        };
        var outboundId = await outboundRepo.InsertReturnSnowflakeIdAsync(outbound, cancellationToken);

        // 新增产品流转记录
        ProdStepExecution stepExecution = new()
        {
            InboundId = inbound.Id,
            OutboundId = outboundId,
            StepId = routingStep.Id,
            Sn = currentState.Sn,
            ProductId = currentState.ProductId,
            ProductCode = currentState.ProductCode,
            Wo = currentState.Wo,
            LineId = inbound.LineId,
            LineCode = workstation.Line!.LineCode,
            RoutingId = inbound.RoutingId,
            OperationId = inbound.OperationId,
            OperationCode = routingStep.Operation!.OpCode,
            WorkstationId = inbound.WorkstationId,
            WorkstationCode = workstation.WsCode,
            InboundTime = inbound.InboundTime,
            OutboundTime = outbound.OutboundTime,
            PassResult = outbound.PassResult,
            CycleTime = outbound.CycleTime,
            ReworkDocId = currentState.InReworkDocId,
        };
        var stepExecutionId = await stepExecutionRepo.InsertReturnSnowflakeIdAsync(stepExecution, cancellationToken);

        // 注：过程数据与工步绑定，要采集的数据都需要设定工步

        // 若是成功下线，检查是否为尾站（尾站更改工单信息）
        // 报废品由人工在线外站手动确认
        bool isCompleted = false;
        if (outbound.IsSuccess() && !string.IsNullOrEmpty(currentState.Wo))
        {
            bool isLastOperation = await routingQueryService.IsLastOperationAsync(inbound.RoutingId, inbound.OperationId); // 检查是否为尾序
            if (isLastOperation)
            {
                // 良品数量+1
                isCompleted = await workOrderService.IncrementGoodQtyAsync(currentState.Wo, cancellationToken);
            }
        }

        // 线上返工，更改返工单状态
        // 思考：若是在线返工，在所有返工工序结束后才设定为完成。
        if (currentState.InReworkDocId > 0)
        {
            // 更新返工单状态，标记为已完成
            var reworkDoc = await reworkDocRepo.GetByIdAsync(currentState.InReworkDocId, cancellationToken);
            if (reworkDoc != null)
            {
                reworkDoc.Status = ReworkStatusEnum.Completed;
                reworkDoc.Result = currentState.IsGood() ? ReworkResultEnum.Qualified : ReworkResultEnum.Unqualified;
                reworkDoc.EndTime = DateTime.Now;
                await reworkDocRepo.UpdateAsync(reworkDoc, m => new
                {
                    m.Status,
                    m.Result,
                    m.EndTime,
                }, cancellationToken);
            }

            // 重置产品当前返工状态与计数
            currentState.InReworkDocId = 0; // 重置返工单
            currentState.ReworkCount++;
        }

        // 更改产品生产进度状态
        currentState.Stage = WorkStageEnum.Out;
        currentState.Status = outbound.PassResult;
        currentState.OutboundTime = outbound.OutboundTime;
        currentState.IsCompelted = isCompleted;
        // 若 NG，NG 次数+1
        if (currentState.IsNotGood())
        {
            currentState.UnfitCount++;
        }

        await currentStateRepo.UpdateAsync(currentState, m => new
        {
            m.Stage,
            m.Status,
            m.OutboundTime,
            m.CompletedTime,
            m.IsCompelted,
            m.UnfitCount,
            m.InReworkDocId,
            m.ReworkCount,
        }, cancellationToken);

        // 根据标准设定找到对应的采集数据，未找到采集值时不记录。
        // 注：工序的详细数据记录在工步过站数据中，工艺步骤过程数据只记录一些归属于工序的数据，如打标的总成码等。
        var standards = await stepStandardRepo.GetListAsync(m => m.StepId == routingStep.Id, cancellationToken);
        if (standards.Count > 0)
        {
            List<ProdStepData> stepDataList = [];
            foreach (var standard in standards)
            {
                var data = input.AttachData.FirstOrDefault(s => s.TagName == standard.Symbol);
                if (data != null)
                {
                    ProdStepData archive = new()
                    {
                        ExecutionId = stepExecutionId,
                        ParamName = standard.ParameterName,
                        ParamCode = standard.ParameterCode,
                        Text = data.GetString(),
                        Unit = standard.MeasurementUnit,
                    };
                    stepDataList.Add(archive);
                }
            }

            if (stepDataList.Count > 0)
            {
                await stepDataRepo.InsertRangeAsync(stepDataList, cancellationToken);
            }
        }

        // 赋予 target 值
        return ScadaResult.Ok(new OutboundOutput
        {
            Sn = stepExecution.Sn,
            StepExecutionId = stepExecutionId,
            LineId = stepExecution.LineId,
            LineCode = stepExecution.LineCode,
            WorkstationId = stepExecution.WorkstationId,
            WorkstationCode = stepExecution.WorkstationCode,
            OperationId = stepExecution.OperationId,
            OperationCode = stepExecution.OperationCode,
            OperationTag = routingStep.Operation!.Tag,
            PassResult = stepExecution.PassResult,
            IsCompelted = currentState.IsCompelted,
        }, null);
    }
}
